#include "pch.h"

// Included first and then again later to ensure that we're able to "light up" new functionality based off new includes
#include <wil/resource.h>

#include <wil/com.h>
#include <wil/stl.h>

// Headers to "light up" functionality in resource.h
#include <memory>
#include <roapi.h>
#include <winstring.h>
#include <WinUser.h>

#include <wil/resource.h> // NOLINT(readability-duplicate-include): Intentional
#include <wrl/implements.h>

#include "common.h"

TEST_CASE("ResourceTests::TestLastErrorContext", "[resource][last_error_context]")
{
    // Destructing the last_error_context restores the error.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
    }
    REQUIRE(GetLastError() == 42);

    // The context can be moved.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        {
            auto another_error42 = wil::last_error_context(std::move(error42));
            SetLastError(1);
        }
        REQUIRE(GetLastError() == 42);
        SetLastError(0);
        // error42 has been moved-from and should not do anything at destruction.
    }
    REQUIRE(GetLastError() == 0);

    // The context can be self-assigned, which has no effect.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        error42 = std::move(error42);
        SetLastError(1);
    }
    REQUIRE(GetLastError() == 42);

    // The context can be dismissed, which cause it to do nothing at destruction.
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(0);
        error42.release();
        SetLastError(1);
    }
    REQUIRE(GetLastError() == 1);

    // The value in the context is unimpacted by other things changing the last error
    {
        SetLastError(42);
        auto error42 = wil::last_error_context();
        SetLastError(1);
        REQUIRE(error42.value() == 42);
    }
}

TEST_CASE("ResourceTests::TestScopeExit", "[resource][scope_exit]")
{
    int count = 0;
    auto validate = [&](int expected) {
        REQUIRE(count == expected);
        count = 0;
    };

    {
        auto foo = wil::scope_exit([&] {
            count++;
        });
    }
    validate(1);

    {
        auto foo = wil::scope_exit([&] {
            count++;
        });
        foo.release();
        foo.reset();
    }
    validate(0);

    {
        auto foo = wil::scope_exit([&] {
            count++;
        });
        foo.reset();
        foo.reset();
        validate(1);
    }
    validate(0);

#ifdef WIL_ENABLE_EXCEPTIONS
    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
            count++;
            THROW_HR(E_FAIL);
        });
    }
    validate(1);

    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
            count++;
            THROW_HR(E_FAIL);
        });
        foo.release();
        foo.reset();
    }
    validate(0);

    {
        auto foo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
            count++;
            THROW_HR(E_FAIL);
        });
        foo.reset();
        foo.reset();
        validate(1);
    }
    validate(0);
#endif // WIL_ENABLE_EXCEPTIONS
}

interface __declspec(uuid("ececcc6a-5193-4d14-b38e-ed1460c20b00")) ITest : public IUnknown
{
    STDMETHOD_(void, Test)() = 0;
};

class __declspec(empty_bases) PointerTestObject
    : witest::AllocatedObject,
      public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>, ITest>
{
public:
    STDMETHOD_(void, Test)() {};
};

TEST_CASE("ResourceTests::TestOperationsOnGenericSmartPointerClasses", "[resource]")
{
#ifdef WIL_ENABLE_EXCEPTIONS
    {
        // wil::unique_any_t example
        wil::unique_event ptr2(wil::EventOptions::ManualReset);
        // wil::com_ptr
        wil::com_ptr<PointerTestObject> ptr3 = Microsoft::WRL::Make<PointerTestObject>();
        // wil::shared_any_t example
        wil::shared_event ptr4(wil::EventOptions::ManualReset);
        // wistd::unique_ptr example
        auto ptr5 = wil::make_unique_failfast<POINT>();

        static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr2)>::pointer, HANDLE>::value, "type-mismatch");
        static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr3)>::pointer, PointerTestObject*>::value, "type-mismatch");

        auto raw2 = wil::detach_from_smart_pointer(ptr2);
        auto raw3 = wil::detach_from_smart_pointer(ptr3);
        // auto raw4 = wil::detach_from_smart_pointer(ptr4); // wil::shared_any_t and std::shared_ptr do not support release().
        HANDLE raw4{};
        auto raw5 = wil::detach_from_smart_pointer(ptr5);

        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((raw2 && raw3));

        wil::attach_to_smart_pointer(ptr2, raw2);
        wil::attach_to_smart_pointer(ptr3, raw3);
        wil::attach_to_smart_pointer(ptr4, raw4);
        wil::attach_to_smart_pointer(ptr5, raw5);

        raw2 = nullptr;
        raw3 = nullptr;
        raw4 = nullptr;
        raw5 = nullptr;

        wil::detach_to_opt_param(&raw2, ptr2);
        wil::detach_to_opt_param(&raw3, ptr3);

        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((raw2 && raw3));

        wil::attach_to_smart_pointer(ptr2, raw2);
        wil::attach_to_smart_pointer(ptr3, raw3);
        raw2 = nullptr;
        raw3 = nullptr;

        wil::detach_to_opt_param(&raw2, ptr2);
        wil::detach_to_opt_param(&raw3, ptr3);
        REQUIRE((!ptr2 && !ptr3));
        REQUIRE((raw2 && raw3));

        [&](decltype(raw2)* ptr) {
            *ptr = raw2;
        }(wil::out_param(ptr2));
        [&](decltype(raw3)* ptr) {
            *ptr = raw3;
        }(wil::out_param(ptr3));
        [&](decltype(raw4)* ptr) {
            *ptr = raw4;
        }(wil::out_param(ptr4));
        [&](decltype(raw5)* ptr) {
            *ptr = raw5;
        }(wil::out_param(ptr5));

        REQUIRE((ptr2 && ptr3));

        // Validate R-Value compilation
        wil::detach_to_opt_param(&raw2, decltype(ptr2){});
        wil::detach_to_opt_param(&raw3, decltype(ptr3){});
    }
#endif

    std::unique_ptr<int> ptr1(new int(1));
    Microsoft::WRL::ComPtr<PointerTestObject> ptr4 = Microsoft::WRL::Make<PointerTestObject>();

    static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr1)>::pointer, int*>::value, "type-mismatch");
    static_assert(wistd::is_same<typename wil::smart_pointer_details<decltype(ptr4)>::pointer, PointerTestObject*>::value, "type-mismatch");

    auto raw1 = wil::detach_from_smart_pointer(ptr1);
    auto raw4 = wil::detach_from_smart_pointer(ptr4);

    REQUIRE((!ptr1 && !ptr4));
    REQUIRE((raw1 && raw4));

    wil::attach_to_smart_pointer(ptr1, raw1);
    wil::attach_to_smart_pointer(ptr4, raw4);

    REQUIRE((ptr1 && ptr4));

    raw1 = nullptr;
    raw4 = nullptr;

    int** pNull = nullptr;
    wil::detach_to_opt_param(pNull, ptr1);
    REQUIRE(ptr1);

    wil::detach_to_opt_param(&raw1, ptr1);
    wil::detach_to_opt_param(&raw4, ptr4);

    REQUIRE((!ptr1 && !ptr4));
    REQUIRE((raw1 && raw4));

    [&](decltype(raw1)* ptr) {
        *ptr = raw1;
    }(wil::out_param(ptr1));
    [&](decltype(raw4)* ptr) {
        *ptr = raw4;
    }(wil::out_param(ptr4));

    REQUIRE((ptr1 && ptr4));

    raw1 = wil::detach_from_smart_pointer(ptr1);
    [&](int** ptr) {
        *ptr = raw1;
    }(wil::out_param_ptr<int**>(ptr1));
    REQUIRE(ptr1);
}

// NOLINTNEXTLINE(misc-use-internal-linkage): Compilation only test...
void StlAdlTest()
{
    // This test has exposed some Argument Dependent Lookup issues in wistd / stl.  Primarily we're
    // just looking for clean compilation.

    std::vector<wistd::unique_ptr<int>> vec;
    vec.emplace_back(new int{1});
    vec.emplace_back(new int{2});
    vec.emplace_back(new int{3});
    std::rotate(begin(vec), begin(vec) + 1, end(vec));

    REQUIRE(*vec[0] == 1);
    REQUIRE(*vec[1] == 3);
    REQUIRE(*vec[2] == 2);

    decltype(vec) vec2;
    vec2 = std::move(vec);
    REQUIRE(*vec2[0] == 1);
    REQUIRE(*vec2[1] == 3);
    REQUIRE(*vec2[2] == 2);

    decltype(vec) vec3;
    std::swap(vec2, vec3);
    REQUIRE(*vec3[0] == 1);
    REQUIRE(*vec3[1] == 3);
    REQUIRE(*vec3[2] == 2);
}

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
// NOLINTNEXTLINE(misc-use-internal-linkage): Compilation only test...
void UniqueProcessInfo()
{
    wil::unique_process_information process;
    CreateProcessW(nullptr, nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, nullptr, &process);
    ResumeThread(process.hThread);
    WaitForSingleObject(process.hProcess, INFINITE);
    wil::unique_process_information other(wistd::move(process));
}
#endif

#ifdef WIL_ENABLE_EXCEPTIONS
// NOLINTNEXTLINE(misc-use-internal-linkage): Compilation only test...
void NoexceptConstructibleTest()
{
    using BaseStorage = wil::details::unique_storage<wil::details::handle_resource_policy>;

    struct ThrowingConstructor : BaseStorage
    {
        ThrowingConstructor() = default;
        explicit ThrowingConstructor(HANDLE) __WI_NOEXCEPT_(false)
        {
        }
    };

    struct ProtectedConstructor : BaseStorage
    {
    protected:
        ProtectedConstructor() = default;
        explicit ProtectedConstructor(HANDLE) WI_NOEXCEPT
        {
        }
    };

    // wil::unique_handle is one of the many types which are expected to be noexcept
    // constructible since they don't perform any "advanced" initialization.
    static_assert(wistd::is_nothrow_default_constructible_v<wil::unique_handle>, "wil::unique_any_t should always be nothrow default constructible");
    static_assert(wistd::is_nothrow_constructible_v<wil::unique_handle, HANDLE>, "wil::unique_any_t should be noexcept if the storage is");

    // The inverse: A throwing storage constructor.
    static_assert(
        wistd::is_nothrow_default_constructible_v<wil::unique_any_t<ThrowingConstructor>>,
        "wil::unique_any_t should always be nothrow default constructible");
    static_assert(
        !wistd::is_nothrow_constructible_v<wil::unique_any_t<ThrowingConstructor>, HANDLE>,
        "wil::unique_any_t shouldn't be noexcept if the storage isn't");

    // With a protected constructor wil::unique_any_t will be unable to correctly
    // "forward" the noexcept attribute, but the code should still compile.
    wil::unique_any_t<ProtectedConstructor> value{INVALID_HANDLE_VALUE};
}
#endif

struct FakeComInterface
{
    void AddRef()
    {
        refs++;
    }
    void Release()
    {
        refs--;
    }

    HRESULT __stdcall Close()
    {
        closes++;
        return S_OK;
    }

    size_t refs = 0;
    size_t closes = 0;

    bool called()
    {
        auto old = closes;
        closes = 0;
        return (old > 0);
    }

    bool has_ref() const
    {
        return (refs > 0);
    }
};

static void __stdcall CloseFakeComInterface(FakeComInterface* fake)
{
    fake->Close();
}

using unique_fakeclose_call = wil::unique_com_call<FakeComInterface, decltype(&CloseFakeComInterface), CloseFakeComInterface>;

TEST_CASE("ResourceTests::VerifyUniqueComCall", "[resource][unique_com_call]")
{
    unique_fakeclose_call call1;
    unique_fakeclose_call call2;

    // intentional compilation errors
    // unique_fakeclose_call call3 = call1;
    // call2 = call1;

    FakeComInterface fake1;
    unique_fakeclose_call call4(&fake1);
    REQUIRE(fake1.has_ref());

    unique_fakeclose_call call5(wistd::move(call4));
    REQUIRE(!call4);
    REQUIRE(call5);
    REQUIRE(fake1.has_ref());

    call4 = wistd::move(call5);
    REQUIRE(call4);
    REQUIRE(!call5);
    REQUIRE(fake1.has_ref());
    REQUIRE(!fake1.called());

    FakeComInterface fake2;
    {
        unique_fakeclose_call scoped(&fake2);
    }
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());

    call4.reset(&fake2);
    REQUIRE(fake1.called());
    REQUIRE(!fake1.has_ref());
    call4.reset();
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());

    call1.reset(&fake1);
    call2.swap(call1);
    REQUIRE((call2 && !call1));

    call2.release();
    REQUIRE(!fake1.called());
    REQUIRE(!fake1.has_ref());
    REQUIRE(!call2);

    REQUIRE(*call1.addressof() == nullptr);

    call1.reset(&fake1);
    fake2.closes = 0;
    fake2.refs = 1;
    *(&call1) = &fake2;
    REQUIRE(!fake1.has_ref());
    REQUIRE(fake1.called());
    REQUIRE(fake2.has_ref());

    call1.reset(&fake1);
    fake2.closes = 0;
    fake2.refs = 1;
    *call1.put() = &fake2;
    REQUIRE(!fake1.has_ref());
    REQUIRE(fake1.called());
    REQUIRE(fake2.has_ref());

    call1.reset();
    REQUIRE(!fake2.has_ref());
    REQUIRE(fake2.called());
}

static bool g_called = false;
static bool called()
{
    auto call = g_called;
    g_called = false;
    return (call);
}

static void __stdcall FakeCall()
{
    g_called = true;
}

using unique_fake_call = wil::unique_call<decltype(&FakeCall), FakeCall>;

TEST_CASE("ResourceTests::VerifyUniqueCall", "[resource][unique_call]")
{
    unique_fake_call call1;
    unique_fake_call call2;

    // intentional compilation errors
    // unique_fake_call call3 = call1;
    // call2 = call1;

    unique_fake_call call4;
    REQUIRE(!called());

    unique_fake_call call5(wistd::move(call4));
    REQUIRE(!call4);
    REQUIRE(call5);

    call4 = wistd::move(call5);
    REQUIRE(call4);
    REQUIRE(!call5);
    REQUIRE(!called());

    {
        unique_fake_call scoped;
    }
    REQUIRE(called());

    call4.reset();
    REQUIRE(called());
    call4.reset();
    REQUIRE(!called());

    call1.release();
    REQUIRE((!call1 && call2));
    call2.swap(call1);
    REQUIRE((call1 && !call2));

    call2.release();
    REQUIRE(!called());
    REQUIRE(!call2);

#ifdef __WIL__ROAPI_H_APPEXCEPTIONAL
    {
        auto call = wil::RoInitialize();
    }
#endif
#ifdef __WIL__ROAPI_H_APP
    {
        wil::unique_rouninitialize_call uninit;
        uninit.release();

        auto call = wil::RoInitialize_failfast();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_APPEXCEPTIONAL
    {
        auto call = wil::CoInitializeEx();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_APP
    {
        wil::unique_couninitialize_call uninit;
        uninit.release();

        auto call = wil::CoInitializeEx_failfast();
    }
#endif
}

// NOLINTNEXTLINE(misc-use-internal-linkage): Compilation only test...
void UniqueCallCompilationTest()
{
#ifdef __WIL__COMBASEAPI_H_EXCEPTIONAL
    {
        auto call = wil::CoImpersonateClient();
    }
#endif
#ifdef __WIL__COMBASEAPI_H_
    {
        wil::unique_coreverttoself_call uninit;
        uninit.release();

        auto call = wil::CoImpersonateClient_failfast();
    }
#endif
}

template <typename StringType, typename VerifyContents>
static void TestStringMaker(VerifyContents&& verifyContents)
{
    PCWSTR values[] = {
        L"",
        L"value",
        // 300 chars
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
        L"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"};

    for (const auto& value : values)
    {
        auto const valueLength = wcslen(value);

        // Direct construction case.
        wil::details::string_maker<StringType> maker;
        THROW_IF_FAILED(maker.make(value, valueLength));
        auto result = maker.release();
        verifyContents(value, valueLength, result);

        // Two phase construction case.
        THROW_IF_FAILED(maker.make(nullptr, valueLength));
        REQUIRE(maker.buffer() != nullptr);
        // In the case of the wil::unique_hstring and the empty string the buffer is in a read only
        // section and can't be written to, so StringCchCopy(maker.buffer(), valueLength + 1, value) will fault adding the nul
        // terminator. Use memcpy_s specifying exact size that will be zero in this case instead.
        memcpy_s(maker.buffer(), valueLength * sizeof(*value), value, valueLength * sizeof(*value));
        result = maker.release();
        verifyContents(value, valueLength, result);

        {
            // no promote, ensure no leaks (not tested here, inspect in the debugger)
            wil::details::string_maker<StringType> maker2;
            THROW_IF_FAILED(maker2.make(value, valueLength));
        }
    }
}

#ifdef WIL_ENABLE_EXCEPTIONS
template <typename StringType>
static void VerifyMakeUniqueString(bool nullValueSupported = true)
{
    if (nullValueSupported)
    {
        auto value0 = wil::make_unique_string<StringType>(nullptr, 5);
    }

    struct
    {
        PCWSTR expectedValue;
        PCWSTR testValue;
        // this is an optional parameter
        size_t testLength = static_cast<size_t>(-1);
    } const testCaseEntries[] = {
        {L"value", L"value", 5},
        {L"value", L"value"},
        {L"va", L"va\0ue", 5},
        {L"v", L"value", 1},
        {L"\0", L"", 5},
        {L"\0", nullptr, 5},
    };

    using maker = wil::details::string_maker<StringType>;
    for (auto const& entry : testCaseEntries)
    {
        bool shouldSkipNullString = ((wcscmp(entry.expectedValue, L"\0") == 0) && !nullValueSupported);
        if (!shouldSkipNullString)
        {
            auto desiredValue = wil::make_unique_string<StringType>(entry.expectedValue);
            auto stringValue = wil::make_unique_string<StringType>(entry.testValue, entry.testLength);
            auto stringValueNoThrow = wil::make_unique_string_nothrow<StringType>(entry.testValue, entry.testLength);
            auto stringValueFailFast = wil::make_unique_string_failfast<StringType>(entry.testValue, entry.testLength);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValue)) == 0);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValueNoThrow)) == 0);
            REQUIRE(wcscmp(maker::get(desiredValue), maker::get(stringValueFailFast)) == 0);
        }
    }
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerCoTaskMem", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_cotaskmem_string>();
    TestStringMaker<wil::unique_cotaskmem_string>([](PCWSTR value, size_t /*valueLength*/, const wil::unique_cotaskmem_string& result) {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerLocalAlloc", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_hlocal_string>();
    TestStringMaker<wil::unique_hlocal_string>([](PCWSTR value, size_t /*valueLength*/, const wil::unique_hlocal_string& result) {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerGlobalAlloc", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_hglobal_string>();
    TestStringMaker<wil::unique_hglobal_string>([](PCWSTR value, size_t /*valueLength*/, const wil::unique_hglobal_string& result) {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerProcessHeap", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_process_heap_string>();
    TestStringMaker<wil::unique_process_heap_string>([](PCWSTR value, size_t /*valueLength*/, const wil::unique_process_heap_string& result) {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}
#endif

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerMidl", "[resource][string_maker]")
{
    VerifyMakeUniqueString<wil::unique_midl_string>();
    TestStringMaker<wil::unique_midl_string>([](PCWSTR value, size_t /*valueLength*/, const wil::unique_midl_string& result) {
        REQUIRE(wcscmp(value, result.get()) == 0);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerHString", "[resource][string_maker]")
{
    wil::unique_hstring value;
    value.reset(static_cast<HSTRING>(nullptr));

    VerifyMakeUniqueString<wil::unique_hstring>(false);

    TestStringMaker<wil::unique_hstring>([](PCWSTR value, size_t valueLength, const wil::unique_hstring& result) {
        UINT32 length;
        REQUIRE(wcscmp(value, WindowsGetStringRawBuffer(result.get(), &length)) == 0);
        REQUIRE(valueLength == length);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyStringMakerStdWString", "[resource][string_maker]")
{
    wil::details::string_maker<std::wstring> maker;

    TestStringMaker<std::wstring>([](PCWSTR value, size_t valueLength, const std::wstring& result) {
        REQUIRE(wcscmp(value, result.c_str()) == 0);
        REQUIRE(result == value);
        REQUIRE(result.size() == valueLength);
    });
}

TEST_CASE("UniqueStringAndStringMakerTests::VerifyLegacyStringMakers", "[resource][string_maker]")
{
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
    auto localStr = wil::make_hlocal_string(L"value");
    localStr = wil::make_hlocal_string_nothrow(L"value");
    localStr = wil::make_hlocal_string_failfast(L"value");

    auto heapStr = wil::make_process_heap_string(L"value");
    heapStr = wil::make_process_heap_string_nothrow(L"value");
    heapStr = wil::make_process_heap_string_failfast(L"value");
#endif
    auto coTaskStr = wil::make_cotaskmem_string(L"value");
    coTaskStr = wil::make_cotaskmem_string_nothrow(L"value");
    coTaskStr = wil::make_cotaskmem_string_failfast(L"value");
}
#endif

_Use_decl_annotations_
void* __RPC_USER MIDL_user_allocate(size_t size)
{
    return ::HeapAlloc(GetProcessHeap(), 0, size);
}

_Use_decl_annotations_
void __RPC_USER MIDL_user_free(void* ptr)
{
    ::HeapFree(GetProcessHeap(), 0, ptr);
}

TEST_CASE("UniqueMidlStringTests", "[resource][rpc]")
{
    wil::unique_midl_ptr<int[]> intArray{reinterpret_cast<int*>(::MIDL_user_allocate(sizeof(int) * 10))};
    intArray[2] = 1;

    wil::unique_midl_ptr<int> intSingle{reinterpret_cast<int*>(::MIDL_user_allocate(sizeof(int) * 1))};
}

TEST_CASE("UniqueEnvironmentStrings", "[resource][win32]")
{
    wil::unique_environstrings_ptr env{::GetEnvironmentStringsW()};
    const wchar_t* nextVar = env.get();
    while (nextVar && *nextVar)
    {
        // consume 'nextVar'
        nextVar += wcslen(nextVar) + 1;
    }

    wil::unique_environansistrings_ptr envAnsi{::GetEnvironmentStringsA()};
    const char* nextVarAnsi = envAnsi.get();
    while (nextVarAnsi && *nextVarAnsi)
    {
        // consume 'nextVar'
        nextVarAnsi += strlen(nextVarAnsi) + 1;
    }
}

#if (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION) || \
    (!defined(_MSC_EXTENSIONS) && !defined(_FORCENAMELESSUNION))
#define VAR_ACCESS_1 .n1
#define VAR_ACCESS_2 .n1.n2
#define VAR_ACCESS_3 .n1.n2.n3
#define VAR_ACCESS_4 .n1.n2.n3.brecVal
#else
#define VAR_ACCESS_1
#define VAR_ACCESS_2
#define VAR_ACCESS_3
#define VAR_ACCESS_4
#endif

TEST_CASE("UniqueVariant", "[resource][com]")
{
    wil::unique_variant var;
    var VAR_ACCESS_2.vt = VT_BSTR;
    var VAR_ACCESS_3.bstrVal = ::SysAllocString(L"25");
    REQUIRE(var VAR_ACCESS_3.bstrVal != nullptr);

    auto call = [](const VARIANT&) {};
    call(var);

    VARIANT weakVar = var;
    (void)weakVar;

    wil::unique_variant var2;
    REQUIRE_SUCCEEDED(VariantChangeType(&var2, &var, 0, VT_UI4));
    REQUIRE(var2 VAR_ACCESS_2.vt == VT_UI4);
    REQUIRE(var2 VAR_ACCESS_3.uiVal == 25);
}

TEST_CASE("DefaultTemplateParamCompiles", "[resource]")
{
    wil::unique_process_heap_ptr<> heapPtr;
    wil::unique_virtualalloc_ptr<> virtualPtr;

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
    wil::unique_hlocal_ptr<> hlocalPtr;
    wil::unique_hlocal_secure_ptr<> hlocalSecurePtr;
    wil::unique_hglobal_ptr<> hglobalPtr;
    wil::unique_cotaskmem_secure_ptr<> cotaskmemSecurePtr;
#endif

    wil::unique_midl_ptr<> midlPtr;
    wil::unique_cotaskmem_ptr<> cotaskmemPtr;
    wil::unique_mapview_ptr<> mapViewPtr;
}

TEST_CASE("UniqueInvokeCleanupMembers", "[resource]")
{
    // Case 1 - unique_ptr<> for a T* that has a "destroy" member
    struct ThingWithDestroy
    {
        bool destroyed = false;
        void destroy()
        {
            destroyed = true;
        };
    };
    ThingWithDestroy toDestroy;
    wil::unique_any<ThingWithDestroy*, decltype(&ThingWithDestroy::destroy), &ThingWithDestroy::destroy> obj(&toDestroy);
    obj.reset();
    REQUIRE(!obj);
    REQUIRE(toDestroy.destroyed);

    // Case 2 - unique_struct calling a member, like above
    struct ThingToDestroy2
    {
        bool* destroyed;
        void destroy() const
        {
            *destroyed = true;
        };
    };
    bool structDestroyed = false;
    {
        wil::unique_struct<ThingToDestroy2, decltype(&ThingToDestroy2::destroy), &ThingToDestroy2::destroy> other;
        other.destroyed = &structDestroyed;
        REQUIRE(!structDestroyed);
    }
    REQUIRE(structDestroyed);
}

struct ITokenTester : IUnknown
{
    virtual void DirectClose(DWORD_PTR token) = 0;
};

struct TokenTester : ITokenTester
{
    IFACEMETHOD_(ULONG, AddRef)() override
    {
        return 2;
    }
    IFACEMETHOD_(ULONG, Release)() override
    {
        return 1;
    }
    IFACEMETHOD(QueryInterface)(REFIID, void**)
    {
        return E_NOINTERFACE;
    }
    void DirectClose(DWORD_PTR token) override
    {
        m_closed = (token == m_closeToken);
    }
    bool m_closed = false;
    DWORD_PTR m_closeToken;
};

static void MyTokenTesterCloser(ITokenTester* obj, DWORD_PTR token)
{
    obj->DirectClose(token);
}

TEST_CASE("ComTokenCloser", "[resource]")
{
    using token_tester_t = wil::unique_com_token<ITokenTester, DWORD_PTR, decltype(MyTokenTesterCloser), &MyTokenTesterCloser>;

    TokenTester obj;
    obj.m_closeToken = 4;
    {
        token_tester_t tmp{&obj, 4};
    }
    REQUIRE(obj.m_closed);
}

TEST_CASE("ComTokenDirectCloser", "[resource]")
{
    using token_tester_t =
        wil::unique_com_token<ITokenTester, DWORD_PTR, decltype(&ITokenTester::DirectClose), &ITokenTester::DirectClose>;

    TokenTester obj;
    obj.m_closeToken = 4;
    {
        token_tester_t tmp{&obj, 4};
    }
    REQUIRE(obj.m_closed);
}

TEST_CASE("UniqueCloseClipboardCall", "[resource]")
{
#if defined(__WIL__WINUSER_) && !defined(NOCLIPBOARD)
    if (auto clip = wil::open_clipboard(nullptr))
    {
        REQUIRE(::EmptyClipboard());
    }
#endif
}
